﻿using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using System.Runtime.Serialization.Formatters.Binary;
using System.Net;
using System;
using System.Net.Security;
using UnityEngine.Networking;
using MobileBaseCommon.ZlibUtility;
using SDK.Scripts.App;
using LuaInterface;
using LuaFramework;
using FrameWork.Scripts.Module;

namespace UnityModule
{
    public enum ReportStep
    {
        startUnZipFile = 99,
        gameCfgFile = 100,
        gcGameUpdateList = 101,
        gcAllCfg = 102,
        ReqCdn = 103,
        UFinish = 104,
        ReqZip = 105,
    }

    /// <summary>
    /// 版本控制
    /// </summary>
    /// <summary>
    /// 版本控制
    /// </summary>
    public partial class VersionComponent : SingletonBehaviour<VersionComponent>
    {
        private UpdateSettings m_UpdateSettings;
        private Action m_OnFinish;

        /// <summary>
        /// cdn上的bundle信息表 key: 无hash的 abname
        /// </summary>
        public Dictionary<string, AssetBundleInfoV2> CdnBundleInfos { get; private set; } = new Dictionary<string, AssetBundleInfoV2>();
        /// <summary>
        /// key: 有hash的 abname, value: 无hash的 abname
        /// </summary>
        public readonly Dictionary<string, string> CdnBundleInfosWithHash = new Dictionary<string, string>();

        GFW_Report Report;

        /// <summary>
        /// 服务器bundle资源信息
        /// </summary>
        private byte[] m_bundleData;

        private void WriteIdFile() {
            File.WriteAllText(Application.persistentDataPath+ "/"+ SystemInfo.deviceUniqueIdentifier + ".id", "1");            
        }

        public void IosCopyLocalResToCdnBundle(Dictionary<string, AssetBundleInfoV2> bundleBinInfo) {

            //保存 bundleNameWithHash->bundleName 对应关系
            foreach (var item in bundleBinInfo)
            {
                item.Value.pathType = 2;
                CdnBundleInfos[item.Key] = item.Value;
                
                CdnBundleInfosWithHash[item.Value.abNameWithHash] = item.Key;
            }
        }

        //初始化
        public void Init()
        {
            m_UpdateSettings = UpdateSettings.Instance;
            SetServerCertificateValidationCallback();
        }

        public void StartCheck(Action onFinish)
        {
            Report = SDK.Scripts.App.GameSDK.ModuleMgr.GetModule("GFW_Report") as GFW_Report;
            m_OnFinish = onFinish;            
            CheckResMode();
        }

        public void ReportFinish(string result)
        {
            Report.SendEvent((int)ReportStep.UFinish, result);
        }

        /// <summary>
        /// http
        /// </summary>
        private void SetServerCertificateValidationCallback()
        {
            //注意这里cdn还没拿到 所以先不判断
            // if (m_UpdateSettings.CDN_PATH.StartsWith("https", StringComparison.OrdinalIgnoreCase))
            {
                //https跳过证书验证
                ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback((sender, certificate, chain, errors) => { return true; });
            }
        }

        //检测资源模式
        /// <summary>
        /// 0.检测资源模型
        /// </summary>
        private void CheckResMode()
        {
            //UpdateUiCtrl.Instance.ShowCircleWaiting(true);
#if UNITY_EDITOR
            if (UpdateSettings.GameSetting.emResType == ResourceMode.EditorLocalRes) //   if (ResourceLoader.Get().EDITOR_MODEL)
            {
                OnFinish();
            }
            else
#endif
            {
                if (UpdateSettings.GameSetting.emResType == ResourceMode.UpdateableBundle)
                {
                    GetClientSetting();
                }
                else if (UpdateSettings.GameSetting.emResType == ResourceMode.LocalBundle) //  if (ResourceLoader.Get().LOCAL_RES)
                {
                    LoadLocalCdnBundleBinInfo(ApplicationPath.ABCachePath + ApplicationPath.BundleInfoFile);
                }
            }
        }

        IEnumerator LoadWebCfg(string url, Action<string> onSuccess, Action<string> onFailed)
        {
            using (UnityWebRequest wwwGetInfo = UnityWebRequest.Get(url))
            {
                wwwGetInfo.timeout = 5;
                yield return wwwGetInfo.SendWebRequest();
                if (string.IsNullOrEmpty(wwwGetInfo.error))
                {
                    string json = wwwGetInfo.downloadHandler.text;
                    onSuccess?.Invoke(json);
                }
                else
                {
                    onFailed?.Invoke(wwwGetInfo.error);
                }
            }
        }

        private float m_progress = 0.4f;
        private void UpdateProgress()
        {
            m_progress += 0.1f;
            PanelLoad.Instance.SetProgress(m_progress);
        }

        private void GetClientSetting() {

            TargetVersion = SDK.Scripts.App.GameSDK.GamePlat.GameVersion;
            
            if (string.IsNullOrEmpty(m_UpdateSettings.UpdateUrl))
            {
                string url = $"http://static.{SDK.Scripts.App.GameSDK.GamePlat.Domain}/gameCfgFile/0/{SDK.Scripts.App.GameSDK.GamePlat.GameId}/G0_V{UpdateSettings.GameSetting.gameUrlVersion}.txt?{SDK.Scripts.App.GameSDK.GamePlat.BaseUrl}";
                //Debug.LogError(url);
                StartCoroutine(LoadWebCfg(url,
                (data) =>
                {
#if UNITY_IOS
                    PanelLoad.Instance.ShowSlider(true);
#endif
                    UpdateProgress();
                    Report.SendEvent((int)ReportStep.gameCfgFile, "1");
                    try
                    {
                        DirectoryTool.SaveFile($"{Application.persistentDataPath}/G0_V{UpdateSettings.GameSetting.gameUrlVersion}.lua", data);

                        LuaManager mgr = LuaHelper.GetLuaManager();
                        LuaTable table = mgr.DoStringCustomTable(data);

                        LuaTable t_WebSet = table.GetTable<LuaTable>("WebSet");
                        
                        LuaTable t_gcGameUpdateList = t_WebSet.GetTable<LuaTable>("gcGameUpdateList");
                        LuaTable t_gcAllCfg = t_WebSet.GetTable<LuaTable>("gcAllCfg");
                        
                        string gcGameUpdateList = t_gcGameUpdateList["weburl"].ToString();
                        gcGameUpdateList = gcGameUpdateList.Replace("{$0}", SDK.Scripts.App.GameSDK.GamePlat.Domain) + $"?{SDK.Scripts.App.GameSDK.GamePlat.BaseUrl}";

                        string gcAllCfg = t_gcAllCfg["weburl"].ToString();
                        gcAllCfg = gcAllCfg.Replace("{$0}", SDK.Scripts.App.GameSDK.GamePlat.Domain) + $"?{SDK.Scripts.App.GameSDK.GamePlat.BaseUrl}";
                        
                        GetGameUpdateList(gcGameUpdateList, gcAllCfg);
                        // Debug.LogError(gcGameUpdateList);
                        // Debug.LogError(gcAllCfg);
                    }
                    catch (Exception)
                    {
                        Report.SendEvent((int)ReportStep.gameCfgFile, "2");
                        GetCdnUrlFailed();
                    }
                }, (exception) =>
                {
                    Report.SendEvent((int)ReportStep.gameCfgFile, "3");
                    GetCdnUrlFailed();
                }));            
            }
            else
            {
                m_UpdateSettings.SetCdnRootUrl(m_UpdateSettings.UpdateUrl);
                ReqCdnInfo();
                //PanelLoad.Instance.ShowTip();
#if UNITY_IOS
                PanelLoad.Instance.ShowSlider(true);
#endif
            }
        }

        public class UpList
        {
            public int ret;
            public string[] data;
        }
        private void GetGameUpdateList(string gcGameUpdateList,string gcAllCfg)
        {
            StartCoroutine(LoadWebCfg(gcGameUpdateList, (arg) =>
            {
                Report.SendEvent((int)ReportStep.gcGameUpdateList, "1");
                try
                {
                    UpdateProgress();
                    UpList ulist = JsonUtility.FromJson<UpList>(arg);
                    if (ulist.ret == 1 && ulist.data.Length > 0)
                    {
                        GetAllCfg(gcAllCfg, ulist.data);
                    }
                    else
                    {
                        Report.SendEvent((int)ReportStep.gcGameUpdateList, "2");
                        GetCdnUrlFailed();
                    }
                }
                catch(Exception )
                {
                    Report.SendEvent((int)ReportStep.gcGameUpdateList, "3");
                    GetCdnUrlFailed();
                }
            }, (exception) =>
            {
                Report.SendEvent((int)ReportStep.gcGameUpdateList, "4");
                GetCdnUrlFailed();
            }));
        }

        [System.Serializable]
        public class UExtra
        {
            public int pid;
            public string url;
            public string extra;
            public int updatetype;
            public int gameid;
        }
        [System.Serializable]
        public class UGamemanage
        {
            public int ret;
            public UExtra[] data;
        }
        [System.Serializable]
        public class UData
        {
            public UGamemanage gc_gamemanage;
        }
        public class UAllCfg
        {
            public int ret;
            public UData data;
        }

        private int GetTargetVersion(string verupdate)
        {
            Report.SendEvent((int)ReportStep.gcAllCfg, verupdate);
            string hello =
                    @"
                        function GetTargetVersion(str,curVersion)
                            local b = loadstring('return '..str)
                            local upTable = b()

                            for k, v in pairs(upTable.verupdate) do
                                if type(v) == 'number' then
                                    if v == curVersion then
                                        return k
                                    end
                                elseif type(v) == 'table' then
                                    if v[1] <= curVersion and v[2] >= curVersion then
                                        return k
                                    end
                                end
                            end
                            return curVersion
                        end
                    ";
            LuaManager mgr = LuaHelper.GetLuaManager();
            mgr.DoStringCustom(hello, "getallcfg");

            //Get the function object
            LuaFunction luaFunc = mgr.GetFunction("GetTargetVersion");
            luaFunc.BeginPCall();
            luaFunc.Push(verupdate);
            luaFunc.Push(SDK.Scripts.App.GameSDK.GamePlat.GameVersion);
            luaFunc.PCall();
            int targetVersion = (int)luaFunc.CheckNumber();
            luaFunc.EndPCall();

            if (luaFunc != null)
            {
                luaFunc.Dispose();
                luaFunc = null;
            }
            return targetVersion;
        }


        public int TargetVersion = 0;
        private void GetAllCfg(string url,string[] str_pids)
        {
            StartCoroutine(LoadWebCfg(url, (arg) =>
            {
                try
                {
                    Report.SendEvent((int)ReportStep.gcAllCfg, "1");
                    UpdateProgress();
                    UAllCfg ucfg = JsonUtility.FromJson<UAllCfg>(arg);
                    string cdnUrl = string.Empty;
                    int updatetype = 3;
                    if (ucfg.ret == 1)
                    {
                        //先找到30009的更新配置，再匹配pids
                        foreach (var item in ucfg.data.gc_gamemanage.data)
                        {
                            if(item.gameid == SDK.Scripts.App.GameSDK.GamePlat.GameId)
                            {
                                foreach (var str_pid in str_pids)
                                {
                                    int tmPid = int.Parse(str_pid);
                                    if (item.pid == tmPid)
                                    {
                                        TargetVersion = GetTargetVersion(item.extra);
                                        cdnUrl = item.url;
                                        updatetype = item.updatetype;
                                        break;
                                    }
                                }
                                break;
                            }
                        }
                        // GameSDK.GamePlat.GameVersion = 90014;
                        // TargetVersion = 90026;

                        m_UpdateSettings.SetCdnRootUrl(cdnUrl + "/" + TargetVersion + "/Assetbundles/");

                        
                        Report.SendEvent((int)ReportStep.gcAllCfg, SDK.Scripts.App.GameSDK.GamePlat.GameVersion.ToString() + " " + TargetVersion.ToString());
#if !UNITY_IOS || HUpdate
                        DownloadComponent.Instance.m_dloadUrl = cdnUrl;
#endif
                        //Debug.LogError(cdnUrl);
                        // Debug.LogError(SDK.Scripts.App.GameSDK.GamePlat.Channel + " " + SDK.Scripts.App.GameSDK.GamePlat.GameVersion + " " + TargetVersion);

                        if (updatetype == 3)
                        {
                            //需要更新
                            if (TargetVersion != SDK.Scripts.App.GameSDK.GamePlat.GameVersion)
                            {
                                ReqCdnInfo();
                            }
                            else
                            {
                                GetCdnUrlFailed();
                            }
                        }
                        else
                        {
                            PanelLoad.Instance.ShowTip();
                        }
                    }
                    else
                    {
                        Report.SendEvent((int)ReportStep.gcAllCfg, "2");
                        GetCdnUrlFailed();
                    }
                }
                catch(Exception )
                {
                    Report.SendEvent((int)ReportStep.gcAllCfg, "3");
                    GetCdnUrlFailed();
                }
                
            }, (exception) =>
            {
                Report.SendEvent((int)ReportStep.gcAllCfg, "4");
                GetCdnUrlFailed();
            }));
        }

        public void GetCdnUrlFailed()
        {
            LoadLocalCdnBundleBinInfo(ApplicationPath.ABCachePath + ApplicationPath.BundleInfoFile);
        }

        public void ReqCdnInfo()
        {
            LoadCdnBundleZipInfo(m_UpdateSettings.CdnUrl + ApplicationPath.BundleZipFile, () =>//+ "?rand=" + Guid.NewGuid().ToString()
            {
                LoadCdnBundleBinInfo(m_UpdateSettings.CdnUrl + ApplicationPath.BundleInfoFile);//?rand=" + Guid.NewGuid().ToString());
            });
        }

        //加载cdn上的资源信息
        /// <summary>
        /// 4.下载Bundle列表
        /// </summary>
        /// <param name="url"></param>
        private void LoadCdnBundleBinInfo(string url)
        {
            P.D("LoadCdnBundleBinInfo: " + url);
            Report.SendEvent((int)ReportStep.ReqCdn, "2");
            StartCoroutine(LoadBundleBinInfo(url, DownloadBundleSuccess, (error) =>
            {
                Report.SendEvent((int)ReportStep.ReqCdn, "3");
                LoadLocalCdnBundleBinInfo(ApplicationPath.ABCachePath + ApplicationPath.BundleInfoFile);
            }));
        }
        private void LoadCdnBundleZipInfo(string url, Action retry)
        {
            P.D("LoadCdnBundleZipInfo: " + url);
            
            StartCoroutine(LoadBundleZipInfo(url, DownloadBundleSuccess, (error) =>
            {
                retry?.Invoke();
            }));
        }

        //最后再保存
        public void SaveBundleAndVersion()
        {
            Report.SendEvent((int)ReportStep.UFinish, "1");
            //保存下载好的bin文件,不管是已经在缓存目录，还是stream 目录，都再写一遍在缓存目录
            DirectoryTool.SaveFile(ApplicationPath.ABCachePath + ApplicationPath.BundleInfoFile, m_bundleData);
            DirectoryTool.SaveFile(ApplicationPath.ABCachePath + ApplicationPath.BundleVersionFile, TargetVersion.ToString());
            SDK.Scripts.App.GameSDK.GamePlat.GameVersion = TargetVersion;
        }

        void DownloadBundleSuccess(Dictionary<string, AssetBundleInfoV2> bundleBinInfo, byte[] data)
        {
            Report.SendEvent((int)ReportStep.ReqCdn, "1");
            m_getCdnUrl = true;
            m_bundleData = data;

            CdnBundleInfos = bundleBinInfo;
            //保存 bundleNameWithHash->bundleName 对应关系
            foreach (var item in CdnBundleInfos)
            {
                CdnBundleInfosWithHash[item.Value.abNameWithHash] = item.Key;
            }
            OnFinish();
        }

        public bool m_getCdnUrl = false;
        /// <summary>
        /// 4.加载本地Bundle列表
        /// </summary>
        /// <param name="path"></param>
        private void LoadLocalCdnBundleBinInfo(string path)
        {
            Debug.Log("LoadLocalCdnBundleBinInfo: " + path);

            if (File.Exists(path))
            {
                byte[] data = ReadLocalBundleInfoFile(path);
                Dictionary<string, AssetBundleInfoV2> bundleBinInfo = DeserializeBundleBin(data);

                CdnBundleInfos = bundleBinInfo;
                //保存 bundleNameWithHash->bundleName 对应关系
                foreach (var item in CdnBundleInfos)
                {
                    CdnBundleInfosWithHash[item.Value.abNameWithHash] = item.Key;
                }
                OnFinish();
            }
            else
            {
                Debug.LogError("LoadLocalCdnBundleBinInfo Error Not Found");
            }
        }

        /// <summary>
        /// 下载失败使用本地资源
        /// </summary>
        public void ReplaceCdnInfo()
        {
            byte[] data = ReadLocalBundleInfoFile(ApplicationPath.ABCachePath + ApplicationPath.BundleInfoFile);
            Dictionary<string, AssetBundleInfoV2> bundleBinInfo = DeserializeBundleBin(data);

            CdnBundleInfos = bundleBinInfo;
            //保存 bundleNameWithHash->bundleName 对应关系
            foreach (var item in CdnBundleInfos)
            {
                CdnBundleInfosWithHash[item.Value.abNameWithHash] = item.Key;
            }            
        }
        /// <summary>
        /// 5.解析Bundle列表文件
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        private static Dictionary<string, AssetBundleInfoV2> DeserializeBundleBin(byte[] data)
        {
            Dictionary<string, AssetBundleInfoV2> bundleBinInfo=null;
            using (MemoryStream ms = new MemoryStream(data))
            {
                BinaryFormatter bf = new BinaryFormatter();
                bundleBinInfo = bf.Deserialize(ms) as Dictionary<string, AssetBundleInfoV2>;
            }
            return bundleBinInfo;
        }
        /// <summary>
        /// 6.完成
        /// </summary>
        private void OnFinish()
        {
            m_OnFinish?.Invoke();
        }


        //解析bin文件
        public static IEnumerator LoadBundleBinInfo(string url, Action<Dictionary<string, AssetBundleInfoV2>,byte[]> onSuccess, Action<string> onFailed)
        {
            using (UnityWebRequest wwwGetInfo = UnityWebRequest.Get(url))
            {
                wwwGetInfo.timeout = 5;
                DownloadMonitor.Watch((int)DownloadMonitor.ReportId.DownloadBundleInfo);
                yield return wwwGetInfo.SendWebRequest();
                if (string.IsNullOrEmpty(wwwGetInfo.error))
                {
                    byte[] data = wwwGetInfo.downloadHandler.data;
                    DownloadMonitor.AutoReport((int)DownloadMonitor.ReportId.DownloadBundleInfo);
                    P.D("下载无压缩版本资源");
                    Dictionary<string, AssetBundleInfoV2> bundleBinInfo = DeserializeBundleBin(data);
                    onSuccess?.Invoke(bundleBinInfo, data);
                }
                else
                {
                    onFailed?.Invoke(wwwGetInfo.error);
                }
            }
        }
        public static IEnumerator LoadBundleZipInfo(string url, Action<Dictionary<string, AssetBundleInfoV2>, byte[]> onSuccess, Action<string> onFailed)
        {
            using (UnityWebRequest wwwGetInfo = UnityWebRequest.Get(url))
            {
                wwwGetInfo.timeout = 5;
                DownloadMonitor.Watch((int)DownloadMonitor.ReportId.DownloadBundleInfoZip);
                yield return wwwGetInfo.SendWebRequest();

                PanelLoad.Instance.SetProgress(0.9f);
                if (string.IsNullOrEmpty(wwwGetInfo.error))
                {
                    byte[] data = wwwGetInfo.downloadHandler.data;
                    DownloadMonitor.AutoReport((int)DownloadMonitor.ReportId.DownloadBundleInfoZip);
                    P.D("下载压缩版本资源");
                    int verifyLen = BitConverter.ToInt32(data, 0);
                    if (verifyLen == data.Length - 8)
                    {
                        ulong srclen = (ulong)BitConverter.ToInt32(data, 4);
                        byte[] zipdata = new byte[verifyLen];
                        Buffer.BlockCopy(data, 8, zipdata, 0, verifyLen);
                        byte[] bundledata = ZlipTool.ZipToByte(zipdata, ref srclen);
                        Dictionary<string, AssetBundleInfoV2> bundleBinInfo = DeserializeBundleBin(bundledata);
                        onSuccess?.Invoke(bundleBinInfo, bundledata);
                    }
                    else
                    {
                        P.E("下载bundle列表zip错误：ErrorSize");
                        onFailed?.Invoke("ErrorSize");
                    }                   
                }
                else
                {
                    DownloadMonitor.Abort((int)DownloadMonitor.ReportId.DownloadBundleInfoZip);
                    P.E("下载bundle列表zip错误："+ wwwGetInfo.error);
                    onFailed?.Invoke(wwwGetInfo.error);
                }
            }
        }
        public static IEnumerator LoadVersionBinInfo(string url, Action<long> onSuccess, Action<string> onFailed)
        {
            using (UnityWebRequest wwwGetInfo = UnityWebRequest.Get(url))
            {
                wwwGetInfo.timeout = 5;
                DownloadMonitor.Watch((int)DownloadMonitor.ReportId.DownloadVersionFile);
                yield return wwwGetInfo.SendWebRequest();
              
                if (string.IsNullOrEmpty(wwwGetInfo.error))
                {
                    string version = wwwGetInfo.downloadHandler.data.RemoveUTF8Bom().ByteToString();
                    long resVersionCode = 0;
                    long.TryParse(version, out resVersionCode);
                    
                    onSuccess?.Invoke(resVersionCode);
                }
                else
                {
                    onFailed?.Invoke(wwwGetInfo.error);
                }
            }
        }
    }
}
